// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/p2p/filtering_network_manager.h"

#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/media_permission.h"

namespace content {

FilteringNetworkManager::FilteringNetworkManager(
    rtc::NetworkManager* network_manager,
    const GURL& requesting_origin,
    media::MediaPermission* media_permission)
    : network_manager_(network_manager)
    , media_permission_(media_permission)
    , requesting_origin_(requesting_origin)
    , weak_ptr_factory_(this)
{
    thread_checker_.DetachFromThread();
    set_enumeration_permission(ENUMERATION_BLOCKED);

    // If the feature is not enabled, just return ALLOWED as it's requested.
    if (!media_permission_) {
        started_permission_check_ = true;
        set_enumeration_permission(ENUMERATION_ALLOWED);
        VLOG(3) << "media_permission is not passed, granting permission";
        return;
    }
}

FilteringNetworkManager::~FilteringNetworkManager()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    // This helps to catch the case if permission never comes back.
    if (!start_updating_time_.is_null())
        ReportMetrics(false);
}

base::WeakPtr<FilteringNetworkManager> FilteringNetworkManager::GetWeakPtr()
{
    return weak_ptr_factory_.GetWeakPtr();
}

void FilteringNetworkManager::Initialize()
{
    rtc::NetworkManagerBase::Initialize();
    if (media_permission_)
        CheckPermission();
}

void FilteringNetworkManager::StartUpdating()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK(started_permission_check_);

    if (start_updating_time_.is_null()) {
        start_updating_time_ = base::TimeTicks::Now();
        network_manager_->SignalNetworksChanged.connect(
            this, &FilteringNetworkManager::OnNetworksChanged);
    }

    // Update |pending_network_update_| and |start_count_| before calling
    // StartUpdating, in case the update signal is fired synchronously.
    pending_network_update_ = true;
    ++start_count_;
    network_manager_->StartUpdating();
}

void FilteringNetworkManager::StopUpdating()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    network_manager_->StopUpdating();
    DCHECK_GT(start_count_, 0);
    --start_count_;
}

void FilteringNetworkManager::GetNetworks(NetworkList* networks) const
{
    DCHECK(thread_checker_.CalledOnValidThread());
    networks->clear();

    if (enumeration_permission() == ENUMERATION_ALLOWED)
        network_manager_->GetNetworks(networks);

    VLOG(3) << "GetNetworks() returns " << networks->size() << " networks.";
}

bool FilteringNetworkManager::GetDefaultLocalAddress(
    int family,
    rtc::IPAddress* ipaddress) const
{
    return network_manager_->GetDefaultLocalAddress(family, ipaddress);
}

void FilteringNetworkManager::CheckPermission()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK(!started_permission_check_);

    started_permission_check_ = true;
    pending_permission_checks_ = 2;

    // Request for media permission asynchronously.
    media_permission_->HasPermission(
        media::MediaPermission::AUDIO_CAPTURE, requesting_origin_,
        base::Bind(&FilteringNetworkManager::OnPermissionStatus, GetWeakPtr()));
    media_permission_->HasPermission(
        media::MediaPermission::VIDEO_CAPTURE, requesting_origin_,
        base::Bind(&FilteringNetworkManager::OnPermissionStatus, GetWeakPtr()));
}

void FilteringNetworkManager::OnPermissionStatus(bool granted)
{
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK_GT(pending_permission_checks_, 0);
    VLOG(3) << "OnPermissionStatus: " << granted;
    IPPermissionStatus old_status = GetIPPermissionStatus();

    --pending_permission_checks_;

    if (granted)
        set_enumeration_permission(ENUMERATION_ALLOWED);

    // If the IP permission status changed *and* we have an up-to-date network
    // list, fire a network change event.
    if (GetIPPermissionStatus() != old_status && !pending_network_update_)
        FireEventIfStarted();
}

void FilteringNetworkManager::OnNetworksChanged()
{
    DCHECK(thread_checker_.CalledOnValidThread());
    pending_network_update_ = false;
    // We wait until our permission status is known before firing a network
    // change signal, so that the listener(s) don't miss out on receiving a
    // full network list.
    if (GetIPPermissionStatus() != PERMISSION_UNKNOWN)
        FireEventIfStarted();
}

void FilteringNetworkManager::ReportMetrics(bool report_start_latency)
{
    if (report_start_latency) {
        ReportTimeToUpdateNetworkList(base::TimeTicks::Now() - start_updating_time_);
    }

    ReportIPPermissionStatus(GetIPPermissionStatus());
}

IPPermissionStatus FilteringNetworkManager::GetIPPermissionStatus() const
{
    if (enumeration_permission() == ENUMERATION_ALLOWED) {
        return media_permission_ ? PERMISSION_GRANTED_WITH_CHECKING
                                 : PERMISSION_GRANTED_WITHOUT_CHECKING;
    }

    if (!pending_permission_checks_ && enumeration_permission() == ENUMERATION_BLOCKED) {
        return PERMISSION_DENIED;
    }

    return PERMISSION_UNKNOWN;
}

void FilteringNetworkManager::FireEventIfStarted()
{
    if (!start_count_)
        return;

    if (!sent_first_update_)
        ReportMetrics(true);

    // Post a task to avoid reentrancy.
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&FilteringNetworkManager::SendNetworksChangedSignal, GetWeakPtr()));

    sent_first_update_ = true;
}

void FilteringNetworkManager::SendNetworksChangedSignal()
{
    SignalNetworksChanged();
}

} // namespace content
